package org.sean.framework.es;

import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.sean.framework.code.StatusInfo;
import org.sean.framework.exception.StatusException;
import org.sean.framework.util.GSONUtil;
import org.sean.framework.util.ObjectUtil;
import org.sean.framework.util.StringUtil;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 查询项
 */
@Data
@Slf4j
public class QueryItem {
    /**
     * 操作类型
     */
    private QueryOption opt;
    //
    private float boost = 1.0f;
    /**
     * 操作字段
     */
    private QueryField field;
    //term查询
    private Integer term;
    //term查询
    private String term4txt;
    // terms查询
    private List<Integer> terms = new ArrayList<>();
    // terms查询
    private List<String> terms4txt = new ArrayList<>();
    // 正则查询
    private String regexp;
    // 通配符查询
    private String wildcard;
    // 全文搜索
    private String stringQuery;
    // 全文搜索
    private String simpleStringQuery;
    // exist
    private Boolean exist;
    // Range
    private Range range;
    private AggregationBuilder aggregate;

    public QueryItem(QueryOption opt, QueryField field) {
        this.opt = opt;
        this.field = field;
    }

    /**
     * exist
     *
     * @param opt   QueryOption
     * @param field QueryField
     * @param exist is exist
     * @return QueryItem
     */
    public static QueryItem create(QueryOption opt, QueryField field, Boolean exist) {
        QueryItem item = new QueryItem(opt, field);
        item.exist = exist;
        return item;
    }

    /**
     * term
     *
     * @param opt   QueryOption
     * @param field QueryField
     * @param term  Term
     * @return QueryItem
     */
    public static QueryItem create(QueryOption opt, QueryField field, Integer term) {
        QueryItem item = new QueryItem(opt, field);
        item.term = term;
        return item;
    }

    /**
     * term
     *
     * @param opt   QueryOption
     * @param field QueryField
     * @param term  Term
     * @return QueryItem
     */
    public static QueryItem create(QueryOption opt, QueryField field, String term) {
        QueryItem item = new QueryItem(opt, field);
        item.term4txt = term;
        return item;
    }

    /**
     * terms
     *
     * @param opt   QueryOption
     * @param field QueryField
     * @param terms Terms
     * @return QueryItem
     */
    public static QueryItem create(QueryOption opt, QueryField field, Collection<Integer> terms) {
        QueryItem item = new QueryItem(opt, field);
        if (ObjectUtil.isEmpty(terms)) {
            return item;
        }
        item.terms.addAll(terms.stream().filter(ObjectUtil::isNotNull).collect(Collectors.toSet()));
        return item;
    }

    /**
     * terms
     *
     * @param opt   QueryOption
     * @param field QueryField
     * @param terms Terms
     * @return QueryItem
     */
    public static QueryItem create4Txt(QueryOption opt, QueryField field, Collection<String> terms) {
        QueryItem item = new QueryItem(opt, field);
        if (ObjectUtil.isEmpty(terms)) {
            return item;
        }
        item.terms4txt.addAll(terms.stream().filter(ObjectUtil::isNotNull).collect(Collectors.toSet()));
        return item;
    }

    /**
     * Regexp
     *
     * @param opt    QueryOption
     * @param field  QueryField
     * @param regexp Regexp
     * @return QueryItem
     */
    public static QueryItem create4Regexp(QueryOption opt, QueryField field, String regexp) {
        QueryItem item = new QueryItem(opt, field);
        item.regexp = regexp;
        return item;
    }

    /**
     * Wildcard
     *
     * @param opt      QueryOption
     * @param field    QueryField
     * @param wildcard Wildcard String
     * @return QueryItem
     */
    public static QueryItem create4Wildcard(QueryOption opt, QueryField field, String wildcard) {
        QueryItem item = new QueryItem(opt, field);
        item.wildcard = wildcard;
        return item;
    }

    /**
     * String Query
     *
     * @param opt   QueryOption
     * @param value String
     * @return QueryItem
     */
    public static QueryItem create4StringQuery(QueryOption opt, String value) {
        QueryItem item = new QueryItem(opt, null);
        item.stringQuery = value;
        return item;
    }

    /**
     * Simple String Query
     *
     * @param opt   QueryOption
     * @param value String
     * @return QueryItem
     */
    public static QueryItem create4SimpleStringQuery(QueryOption opt, String value) {
        QueryItem item = new QueryItem(opt, null);
        item.simpleStringQuery = value;
        return item;
    }

    /**
     * Range
     *
     * @param field QueryField
     * @param opt   QueryOption
     * @param range Range
     * @return QueryItem
     */
    public static QueryItem create(QueryOption opt, QueryField field, Range range) {
        QueryItem item = new QueryItem(opt, field);
        item.range = range;
        return item;
    }

    /**
     * 聚合
     *
     * @param aggregate AggregationBuilder
     * @return QueryItem
     */
    public static QueryItem createAggregation(AggregationBuilder aggregate) {
        QueryItem item = new QueryItem(QueryOption.AGGREGATION, null);
        item.aggregate = aggregate;
        return item;
    }

    /**
     * Boost
     *
     * @param boost boost
     * @return QueryItem
     */
    public QueryItem setBoost(float boost) {
        this.boost = boost;
        return this;
    }

    /**
     * 创建查询条件
     *
     * @return QueryBuilder
     */
    public QueryBuilder toQueryBuilder() {
        if (StringUtil.isNotEmpty(stringQuery)) {
            return QueryBuilders.queryStringQuery(stringQuery);
        } else if (StringUtil.isNotEmpty(simpleStringQuery)) {
            return QueryBuilders.simpleQueryStringQuery(simpleStringQuery);
        }

        String name = field.getName();
        if (term != null) {
            return QueryBuilders.termQuery(name, term);
        } else if (ObjectUtil.isNotEmpty(terms)) {
            return QueryBuilders.termsQuery(name, terms);
        } else if (StringUtil.isNotEmpty(term4txt)) {
            return QueryBuilders.termQuery(name, term4txt);
        } else if (ObjectUtil.isNotEmpty(terms4txt)) {
            return QueryBuilders.termsQuery(name, terms4txt);
        } else if (Boolean.TRUE.equals(exist)) {
            return QueryBuilders.existsQuery(name);
        } else if (StringUtil.isNotEmpty(regexp)) {
            return QueryBuilders.regexpQuery(name, regexp);
        } else if (StringUtil.isNotEmpty(wildcard)) {
            return QueryBuilders.wildcardQuery(name, wildcard);
        } else if (range != null) {
            RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery(name);
            if (range.to != null && range.from != null) {
                rangeQueryBuilder.from(range.from).to(range.to)
                        .includeLower(range.includeLower)
                        .includeUpper(range.includeUpper);
            } else {
                int error = 0;
                if (range.gt != null) {
                    rangeQueryBuilder.gt(range.gt);
                } else if (range.gte != null) {
                    rangeQueryBuilder.gte(range.gte);
                } else {
                    error++;
                }
                if (range.lt != null) {
                    rangeQueryBuilder.lt(range.lt);
                } else if (range.lte != null) {
                    rangeQueryBuilder.lte(range.lte);
                } else {
                    error++;
                }
                if (error == 2) {
                    throw new StatusException(StatusInfo.paramsInvalidError().setMessage("Range条件异常"));
                }
            }
            return rangeQueryBuilder;
        } else {
            log.warn("不支持的查询条件:{}", GSONUtil.obj2Json(this));
            throw new StatusException(StatusInfo.paramsInvalidError().setMessage("不支持的查询条件"));
        }
    }

    /**
     * 聚合查询
     */
    @Data
    public static class Aggregate {
        private String termName;
        private String avg;
        private String max;
        private String min;
        private String sum;
        private QueryField field;
        private Aggregate sub;

        public Aggregate(String name, QueryField field) {
            this.termName = name;
            this.field = field;
        }
    }

    /**
     * 范围查询
     */
    @Data
    @Builder
    public static class Range {
        private Object from;
        private Object to;
        private Object gt;
        private Object gte;
        private Object lt;
        private Object lte;
        @Builder.Default
        private boolean includeLower = true;
        @Builder.Default
        private boolean includeUpper = true;
    }
}
